package org.syntaxlisp.core.match.tree;

import org.syntaxlisp.core.ParseException;
import org.syntaxlisp.core.utils.Pair;
import lombok.Data;

import java.util.*;

import static org.syntaxlisp.core.match.tree.NewMatchSeqNode.*;

/**
 * @author HuangYijun
 * @data 2023-09-13 11:05
 **/
@Data
public class NewRefFrame {

    private final int startPos;

    private final String def;

    private final CurPvtProvider pvtProvider;

    /**
     * 以 (start, end, MatchState) ，这里 (start, end, MatchState) 映射 Pvt List 在于会存在同一个 Repeat State 但是 Match
     * Time 不一样的情况
     */
    private Map<Integer, Map<Integer, Map<MatchState, List<NewCurPvt>>>> pvtMap;

    private Map<Integer, Map<MatchState, NewMatchSeqNode>> seqRootMap;

    public static final int REDUCING = -1;

    public NewRefFrame (int startPos, String def, CurPvtProvider pvtProvider) {
        this.startPos = startPos;
        this.def = def;
        this.pvtProvider = pvtProvider;
        pvtMap = new HashMap<>();
        seqRootMap = new HashMap<>();
    }

    public void putMatchSeqNodeAndPvt (NewMatchSeqNode curRes, NewCurPvt lastPvt) {
        if (lastPvt != null && lastPvt.getBelongsToWho() != this) {
            throw new ParseException("Can not put pvt to the ref frame which lastPvt not belongs to!");
        }
        Objects.requireNonNull(curRes, "NewMatchSeqNode res can not be null.");
        Objects.requireNonNull(curRes.getMatchState(), "NewMatchSeqNode res match state can not be null.");
        if (!curRes.getMatchState().getBelongsToDef().equals(def)) {
            throw new ParseException("Can not put MatchSeqNode to the ref frame which MatchSeqNode not belongs to!");
        }

        NewCurPvt putTargetPvt = genFromMatchSeqNode(curRes);
        // copy repeat stack
        if (lastPvt != null) {
            putTargetPvt.setRepeatStack(lastPvt.copyRepeatStack());
        }
        if (curRes.isReducing()) {
            if (curRes.getStart() != startPos) {
                if (lastPvt == null) {
                    throw new ParseException("未收敛 Ref Pvt 加入 body 情况下 lastPvt 不能为空!");
                }
                putTargetPvt.setCurMatchNode(lastPvt.getCurMatchNode());
                // lastPvt.getCurMatchNode() 增加 pvt ref
                putTargetPvt.getCurMatchNode().addPvtRef(putTargetPvt);
            }
            if (!checkIsContainsPvt(putTargetPvt)) {
                putToPvtMapAndPvtProvider(putTargetPvt);
            }
        } else {
            if (curRes.getStart() == startPos) {
                if (!containSeqRoot(curRes.getEnd(), curRes.getMatchState())) {
                    if (checkIsContainsPvt(putTargetPvt)) {
                        throw new ParseException("未重入的 SeqRoot 却发现重入的 Pvt!");
                    }
                    putToSeqRoot(curRes);
                    putToPvtMapAndPvtProvider(putTargetPvt);
                }
            } else {
                if (lastPvt == null) {
                    throw new ParseException("已收敛 Pvt 加入 body 情况下 lastPvt 不能为空!");
                }
                Pair<Boolean, NewCurPvt> containPvtRes = lookForPvt(putTargetPvt);
                if (Boolean.TRUE.equals(containPvtRes.getFirst())) {
                    NewCurPvt originalPvt = containPvtRes.getSecond();
                    bindMatchSeqNode(lastPvt.getCurMatchNode(), originalPvt.getCurMatchNode());
                } else {
                    putToPvtMapAndPvtProvider(putTargetPvt);
                    bindMatchSeqNode(lastPvt.getCurMatchNode(), putTargetPvt.getCurMatchNode());
                }
            }
        }
    }

    private NewCurPvt genFromMatchSeqNode (NewMatchSeqNode matchSeqNode) {
        return NewCurPvt.builder()
                        .curState(matchSeqNode.getMatchState())
                        .belongsToWho(this)
                        .curMatchNode(matchSeqNode)
                        .curOffset(matchSeqNode.getStart())
                        .nextOffset(matchSeqNode.getEnd())
                        .build();
    }

    private void putToSeqRoot (NewMatchSeqNode target) {
        seqRootMap.computeIfAbsent(target.getEnd(), end -> new HashMap<>())
                  .putIfAbsent(target.getMatchState(), target);
    }

    private void putToPvtMapAndPvtProvider(NewCurPvt curPvt) {
        pvtMap.computeIfAbsent(curPvt.getCurOffset(), start -> new HashMap<>())
              .computeIfAbsent(curPvt.getNextOffset(), end -> new HashMap<>())
              .computeIfAbsent(curPvt.getCurState(), state -> new ArrayList<>())
              .add(curPvt);
        if (curPvt.isReducing()) {
            pvtProvider.offerRefPvt(curPvt);
        } else {
            pvtProvider.offer(curPvt);
        }
    }

    private void bindMatchSeqNode(NewMatchSeqNode last, NewMatchSeqNode next) {
        if (last.isReducing()) {
            throw new ParseException("Reducing MatchSeqNode can not be bounded as the last node!");
        }
        boolean successBind;
        boolean addUpper = next.addUpper(last);
        if (next.isReducing()) {
            boolean addReducing = last.addReducingNetSet(next);
            successBind = (addUpper && addReducing) || (!addUpper && !addReducing);
        } else {
            boolean addNext = last.addNext(next);
            successBind = (addUpper && addNext) || (!addUpper && !addNext);
        }
        if (!successBind) {
            throw new ParseException("add next 与 add upper 结果不一致！");
        }
    }

    private boolean checkIsContainsPvt(NewCurPvt pvt) {
        return lookForPvt(pvt).getFirst();
    }

    /**
     * pvt 在当前 RefFrame 是否重入
     * @param pvt 目标 pvt
     * @return 二元组 (TRUE, 检索到的 pvt), (FALSE, null)
     */
    private Pair<Boolean, NewCurPvt> lookForPvt(NewCurPvt pvt) {
        List<NewCurPvt> startStatePvtList = pvtMap.getOrDefault(pvt.getCurOffset(),
                                                                Collections.emptyMap())
                                                  .getOrDefault(pvt.getNextOffset(),
                                                                Collections.emptyMap())
                                                  .getOrDefault(pvt.getCurState(),
                                                                Collections.emptyList());
        for (NewCurPvt curPvt : startStatePvtList) {
            if (curPvt.isSameRepeat(pvt)) {
                return Pair.<Boolean, NewCurPvt>builder()
                                 .first(true)
                                 .second(curPvt)
                                 .build();
            }
        }
        return Pair.<Boolean, NewCurPvt>builder()
                         .first(false)
                         .build();
    }

    public void putRefAsStart(NewMatchSeqNode reducedDefRes, Map<String, Set<MatchState>> firstRefStatesMap) {
        if (reducedDefRes.getStart() != startPos) {
            throw new ParseException("Can not put start ref pvt to ref frame for their different start positions!");
        }
        // 定位 first ref states in body
        firstRefStatesMap.getOrDefault(getDef(), Collections.emptySet())
                         .stream()
                         .filter(firstRefState -> firstRefState.isSameRef(reducedDefRes.getDef()))
                         .forEach(firstRefState -> {
                             if (!containsRefStart(reducedDefRes.getStart(), reducedDefRes.getEnd(), firstRefState)) {
                                 NewMatchSeqNode curNode = reducedDefRes.copy();
                                 curNode.setMatchState(firstRefState);
                                 putMatchSeqNodeAndPvt(curNode, null);
                             }
                         });
    }

    private boolean containsRefStart (int startOffset, int endOffset, MatchState startRefState) {
        return startOffset == startPos
               && seqRootMap.getOrDefault(endOffset, Collections.emptyMap())
                            .containsKey(startRefState);
    }

    public void reduceRefPvt (NewCurPvt curPvt, NewMatchSeqNode reducedSeqNode) {
        int curOffset = reducedSeqNode.getStart();
        int nextOffset = reducedSeqNode.getEnd();
        NewMatchSeqNode refMatchNode = NewMatchSeqNode.builder()
                                                      .matchState(curPvt.getCurState())
                                                      .type(SeqNodeType.DEF)
                                                      .subLastSeqNode(reducedSeqNode.getSubLastSeqNode())
                                                      .start(curOffset)
                                                      .end(nextOffset)
                                                      .build();
        putMatchSeqNodeAndPvt(refMatchNode, curPvt);
    }

    public void removePvt (NewCurPvt curPvt) {
        Map<MatchState, List<NewCurPvt>> startMap
                = pvtMap.getOrDefault(curPvt.getCurOffset(), Collections.emptyMap())
                        .getOrDefault(curPvt.getNextOffset(), Collections.emptyMap());
        if (!startMap.isEmpty()) {
            List<NewCurPvt> pvtList = startMap.getOrDefault(curPvt.getCurState(), Collections.emptyList());
            if (!pvtList.isEmpty()) {
                if (pvtList.remove(curPvt)) {
                    removeMatchSeqNode(curPvt.getCurMatchNode());
                }
            } else {
                startMap.remove(curPvt.getCurState());
            }
        }
        pvtMap.remove(curPvt.getCurOffset());
    }

    private void removeMatchSeqNode (NewMatchSeqNode target) {
        Queue<NewMatchSeqNode> removeQueue = new ArrayDeque<>();
        removeQueue.offer(target);
        while (!removeQueue.isEmpty()) {
            NewMatchSeqNode cur = removeQueue.poll();
            if (cur.canBeRemoved()) {
                if (cur.isRoot()) {
                    removeSeqRoot(cur);
                } else {
                    for (NewMatchSeqNode upper : cur.getPrevSet()) {
                        upper.getNextSet().remove(cur);
                        removeQueue.offer(upper);
                    }
                }
            } else {
                break;
            }
        }
    }

    private void removeSeqRoot (NewMatchSeqNode target) {
        Integer endOffset = target.getEnd();
        Map<MatchState, NewMatchSeqNode> rootMap = seqRootMap.getOrDefault(endOffset, Collections.emptyMap());
        rootMap.remove(target.getMatchState());
        if (rootMap.isEmpty()) {
            seqRootMap.remove(endOffset);
        }
    }

    private boolean containSeqRoot (int endOffset, MatchState matchState) {
        return seqRootMap.getOrDefault(endOffset, Collections.emptyMap())
                         .containsKey(matchState);
    }

    public String toString() {
        return def + "," + startPos;
    }

}
